#include <llist.h>

LLIST *llist_create(int size, llist_show_t *show, llist_cmp_t *cmp, llist_destroy_t *destroy, llist_file_t *store)
{
    LLIST  *llist = NULL;

    llist = (LLIST *)malloc(sizeof(LLIST));
    ERRP(NULL == llist, goto ERR1, 2, "malloc LLIST failed!\n");

    memset(llist, 0, sizeof(LLIST));

    llist->tmp = (void *)malloc(size);
    ERRP(NULL == llist->tmp, goto ERR2, 2, "malloc tmp failed!\n");

    llist->size = size;
    llist->show = show;
    llist->cmp = cmp;
    llist->destroy = destroy;
    llist->store = store;

    llist->head.next = &llist->head;
    llist->head.prev = &llist->head;

    llist->find = (LLIST *)malloc(sizeof(LLIST));
    ERRP(NULL == llist->find, goto ERR3, 2, "malloc find failed!\n");
    memset(llist->find, 0, sizeof(LLIST));
    llist->find->size = sizeof(void *);
    llist->find->head.next = &llist->find->head;
    llist->find->head.prev = &llist->find->head;

    return llist;
ERR3:
    free(llist->tmp);
ERR2:
    free(llist);
ERR1:
    return NULL;
}

llist_show_t *llist_set_show(llist_show_t *show, LLIST *handle)
{
    llist_show_t *old = handle->show;
    handle->show = show;
    return old;
}

llist_cmp_t *llist_set_cmp(llist_cmp_t *cmp, LLIST *handle)
{
    llist_cmp_t *old = handle->cmp;
    handle->cmp = cmp;
    return old;
}

llist_destroy_t *llist_set_destroy(llist_destroy_t *destroy, LLIST *handle)
{
    llist_destroy_t *old = handle->destroy;
    handle->destroy = destroy;
    return old;
}

static int _insert(void *data, int index, llist_cmp_t *cmp, LLIST *handle)
{
    NODE *new = NULL, *tail = NULL;

    if (index == LLIST_PREPEND || index < LLIST_INSERT_SORT)
        tail = &handle->head;
    else if (index == LLIST_APPEND || index >= handle->num)
        tail = handle->head.prev;
    else if (index == LLIST_INSERT_SORT)
    {
        for (tail = &handle->head; tail->next != &handle->head && cmp(tail->next->data, data) < 0; tail = tail->next)
            continue;
    }
    else
    {
        for (tail = &handle->head; index-- && tail->next != &handle->head; tail = tail->next)
            continue;
    }

    new = (NODE *)malloc(sizeof(NODE));
    ERRP(NULL == new, goto ERR1, 2, "malloc new node failed!\n");

    new->data = (void *)malloc(handle->size);
    ERRP(NULL == new->data, goto ERR2, 2, "malloc new data failed!\n");

    memcpy(new->data, data, handle->size);

    new->next = tail->next;
    new->prev = tail;
    tail->next->prev = new;
    tail->next = new;
    handle->num++;

    return 0;
ERR2:
    free(new);
ERR1:
    return -1;
}

int llist_append(void *data, LLIST *handle)
{
    return _insert(data, LLIST_APPEND, handle->cmp, handle);
}

int llist_prepend(void *data, LLIST *handle)
{
    return _insert(data, LLIST_PREPEND, handle->cmp, handle);
}

int llist_insert_sort_cmp(void *data, llist_cmp_t *cmp, LLIST *handle)
{
    if (cmp == NULL)
        return -1;

    return _insert(data, LLIST_INSERT_SORT, cmp, handle);
}

int llist_insert_sort(void *data, LLIST *handle)
{
    return llist_insert_sort_cmp(data, handle->cmp, handle);
}

int llist_insert(void *data, int index, LLIST *handle)
{
    return _insert(data, index, handle->cmp, handle);
}

static int _node_delete(NODE *tail, LLIST *handle)
{
    tail->next->prev = tail->prev;
    tail->prev->next = tail->next;
    if (handle->destroy)
        handle->destroy(tail->data);
    free(tail->data);
    free(tail);
    handle->num--;

    return 0;
}

int llist_ind_delete(int index, LLIST *handle)
{
    NODE *tail = handle->head.next;

    ERRP(index < 0 || index >= handle->num, return -1, 2, "delete failed!\n");

    while (index--)
    {
        tail = tail->next;
    }

    return _node_delete(tail, handle);
}

int llist_delete_cmp(void *key, llist_cmp_t *cmp, int flag, LLIST *handle)
{
    int count = 0;
    NODE *tail = NULL, *save = NULL;

    if (cmp == NULL)
        return -1;

    for (tail = handle->head.next; tail != &handle->head;  tail = save)
    {
        save = tail->next;
        if (!cmp(key, tail->data))
        {
            if (flag < 0)
            {
                count--;
                if (flag == count)
                {
                    ERRP(_node_delete(tail, handle), goto ERR1, 0);
                    break;
                }
                continue;
            }

            ERRP(_node_delete(tail, handle), goto ERR1, 0);
            count++;
            if (flag == count)
                break;
        }
    }

    if (count < 0)
        return 1;
    return count;
ERR1:
    if (count < 0)
        return 0;
    return count;
}

int llist_delete(void *key, int flag, LLIST *handle)
{
    return llist_delete_cmp(key, handle->cmp, flag, handle);
}

//===============================================

void *llist_ind(int index, LLIST *handle)
{
    NODE *tail = NULL;

    ERRP(index < 0 || index >= handle->num, return NULL, 2, "index error!\n");

    for (tail = handle->head.next; index-- && tail != &handle->head; tail = tail->next)
        continue;

    return tail->data;
}

int llist_size(LLIST *handle)
{
    return handle->size;
}

int llist_num(LLIST *handle)
{
    return handle->num;
}

void llist_travel_show(llist_show_t *show, void *arg, LLIST *handle)
{
    NODE *tail = NULL;

    if (show == NULL)
        return;

    for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
    {
        show(tail->data, arg);
    }
}

void llist_travel(void *arg, LLIST *handle)
{
    llist_travel_show(handle->show, arg, handle);
}

void *llist_travel_data(LLIST *handle)
{
    void *data = NULL;

    if (handle->data_count == NULL)
        handle->data_count = handle->head.next;
    else if (handle->data_count == &handle->head)
        return NULL;

    data = handle->data_count->data;

    handle->data_count = handle->data_count->next;

    return data;
}

void llist_travel_rewind(LLIST *handle)
{
    handle->data_count = handle->head.next;
}

int llist_find_cmp(void *key, llist_cmp_t *cmp, int flag, LLIST *handle)
{
    int count = 0;
    NODE *tail = NULL;

    if (cmp == NULL)
        return -1;

    if (handle->find->num != 0)
        llist_clean(handle->find);

    for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
    {
        if (!cmp(key, tail->data))
        {
            if (flag < 0)
            {
                count--;
                if (flag == count)
                {
                    ERRP(llist_append(&tail->data, handle->find), goto ERR1, 0);
                    break;
                }
                continue;
            }

            ERRP(llist_append(&tail->data, handle->find), goto ERR1, 0);

            count++;
            if (flag == count)
                break;
        }
    }

    handle->find_data_count = handle->find->head.next;

    if (count < 0)
        return 1;
    return count;
ERR1:
    if (count < 0)
        return 0;
    return count;
}

int llist_find(void *key, int flag, LLIST *handle)
{
    return llist_find_cmp(key, handle->cmp, flag, handle);
}

void llist_travel_find_show(llist_show_t *show, void *arg, LLIST *handle)
{
    NODE *tail = NULL;
    LLIST *find = handle->find;

    if (show == NULL)
        return;

    for (tail = handle->find->head.next; tail != &handle->find->head; tail = tail->next)
    {
        show(*(void **)(tail->data), arg);
    }
}

void llist_travel_find(void *arg, LLIST *handle)
{
    llist_travel_find_show(handle->show, arg, handle);
}

void *llist_travel_find_data(LLIST *handle)
{
    void *data = NULL;

    if (handle->find_data_count == NULL)
        handle->find_data_count = handle->find->head.next;
    else if (handle->find_data_count == &handle->find->head)
        return NULL;

    data = handle->find_data_count->data;

    handle->find_data_count = handle->find_data_count->next;

    return *(void **)data;
}

void llist_travel_find_rewind(LLIST *handle)
{
    handle->find_data_count = handle->find->head.next;
}

static void _destroy_node(NODE *head, llist_destroy_t *destroy)
{
    NODE *tail = head->next, *save = NULL;

    while (tail != head)
    {
        save = tail->next;
        if (destroy)
            destroy(tail->data);
        free(tail->data);
        free(tail);
        tail = save;
    }
}

void llist_clean(LLIST *handle)
{
    _destroy_node(&handle->head, handle->destroy);

    handle->num = 0;
    handle->head.next = &handle->head;
    handle->head.prev = &handle->head;
    handle->data_count = handle->head.next;

    _destroy_node(&handle->find->head, NULL);
    handle->find->num = 0;
    handle->find->head.next = &handle->find->head;
    handle->find->head.prev = &handle->find->head;
    handle->find_data_count = handle->find->head.next;
}

int llist_sort_cmp(int flag, llist_cmp_t *cmp, LLIST *handle)
{
    NODE *i = NULL, *j = NULL;
    void *t = NULL;

    if (cmp == NULL)
        return -1;

    for (i = handle->head.next; i->next != &handle->head; i = i->next)
    {
        for (j = i->next; j != &handle->head; j = j->next)
        {
            if ((flag == LLIST_SORT_ASC && cmp(i->data, j->data) > 0)
                 || (flag == LLIST_SORT_DESC && cmp(i->data, j->data) < 0))
            {
                t = i->data;
                i->data = j->data;
                j->data = t;
            }
        }
    }
    return 0;
}

int llist_sort(int flag, LLIST *handle)
{
    return llist_sort_cmp(flag, handle->cmp, handle);
}

void llist_shift_left(int bit, LLIST *handle)
{
    NODE *tail = NULL;

    while (bit--)
    {
        tail = handle->head.next;

        tail->next->prev = tail->prev;
        tail->prev->next = tail->next;

        tail->next = &handle->head;
        tail->prev = handle->head.prev;
        handle->head.prev->next = tail;
        handle->head.prev = tail;
    }
}

void llist_shift_right(int bit, LLIST *handle)
{
    int i;
    NODE *tail = NULL;

    while (bit--)
    {
        tail = handle->head.prev;

        tail->next->prev = tail->prev;
        tail->prev->next = tail->next;

        tail->next = handle->head.next;
        tail->prev = &handle->head;
        handle->head.next->prev = tail;
        handle->head.next = tail;
    }
}

void llist_rev(LLIST *handle)
{
    NODE *tail = &handle->head, *save = NULL;
    NODE *t = NULL;

    while (tail->next != &handle->head)
    {
        save = tail->next;

        t = tail->next;
        tail->next = tail->prev;
        tail->prev = t;

        tail = save;
    }

    t = tail->next;
    tail->next = tail->prev;
    tail->prev = t;
}

static FILE *_safe_fopen(const char *file, const char *mode)
{
    FILE *fp = NULL;
    char ch;

    if (strchr(mode, 'w'))
    {
        fp = fopen(file, "r");
        if (fp != NULL)
        {
            fclose(fp);
            KYO_GETVAL(ch, "%c", "是否覆盖(Y/N): ");
            /* printf("ch = %c\n", ch); */
            if (!(ch == 'Y' || ch == 'y'))
                return NULL;
        }
    }

    return fopen(file, mode);
}

static unsigned short _get_check_sum(DF_HEAD *head)
{
    return (head->num + head->size + MAGIC_LEN + sizeof(DF_HEAD)) % 65536;
}

static DF_HEAD *_pre_head_data(DF_HEAD *head)
{
    strcpy(head->magic, MAGIC);

    head->check_sum = _get_check_sum(head);

    return head;
}

int llist_store(const char *file, LLIST *handle)
{
    FILE *fp = NULL;
    DF_HEAD *head = (DF_HEAD *)handle;
    int ret;
    NODE *tail = NULL;

    fp = _safe_fopen(file, "w");
    ERRP(NULL == fp, goto ERR1, 2, "fopen %s failed!\n", file);

    ret = fwrite(_pre_head_data(head), sizeof(DF_HEAD), 1, fp);
    ERRP(ret != 1, goto ERR2, 2, "fwrite magic failed!\n");

    if (handle->store)
    {
        for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
        {
            ret = handle->store(tail->data, fp);
            ERRP(-1 == ret, goto ERR2, 2, "store data failed!\n");
        }
    }
    else
    {
        for (tail = handle->head.next; tail != &handle->head; tail = tail->next)
        {
            ret = fwrite(tail->data, handle->size, 1, fp);
            ERRP(ret != 1, goto ERR2, 2, "store data failed!\n");
        }
    }

    fclose(fp);
    return 0;
ERR2:
    fclose(fp);
ERR1:
    return -1;
}

LLIST *llist_load(const char *file, llist_show_t *show, llist_cmp_t *cmp, llist_destroy_t *destroy, llist_file_t *store, llist_file_t *load)
{
    FILE *fp = NULL;
    LLIST *handle = NULL;
    DF_HEAD head;
    int ret, i;

    fp = _safe_fopen(file, "r");
    ERRP(NULL ==fp, goto ERR1, 2, "fopen %s failed!\n", file);

    ret = fread(&head, sizeof(DF_HEAD), 1, fp);
    ERRP(ret != 1
            || strcmp(MAGIC, head.magic)
            || head.check_sum != _get_check_sum(&head), goto ERR2, 0);


    handle = llist_create(head.size, show, cmp, destroy, store);
    ERRP(NULL == handle, goto ERR2, 2, "load llist_create failed!\n");

    if (load)
    {
        for (i = 0; i < head.num; i++)
        {
            ret = load(handle->tmp, fp);
            ERRP(-1 == ret, goto ERR3, 2, "load data failed!\n");
            ret = llist_append(handle->tmp, handle);
            ERRP(-1 == ret, goto ERR3, 2, "load append data failed!\n");
        }
    }
    else
    {
        for (i = 0; i < head.num; i++)
        {
            ret = fread(handle->tmp, head.size, 1, fp);
            ERRP(ret != 1, goto ERR3, 2, "fread data failed!\n");
            ret = llist_append(handle->tmp, handle);
            ERRP(-1 == ret, goto ERR3, 2, "load append data failed!\n");
        }
    }

    fclose(fp);

    return handle;
ERR3:
    llist_destroy(&handle);
ERR2:
    fclose(fp);
ERR1:
    return NULL;
}


void llist_destroy(LLIST **my_handle)
{
    LLIST *handle = *my_handle;

    llist_clean(handle);

    free(handle->tmp);
    free(handle->find);
    free(handle);
    *my_handle = NULL;
}

